package cn.yunyichina.openapi.proxy.http;

import cn.yunyichina.openapi.proxy.config.HttpServerConfig;
import cn.yunyichina.openapi.proxy.entity.Response;
import cn.yunyichina.openapi.proxy.filter.FilterHandler;
import cn.yunyichina.openapi.proxy.utils.AuthorizeUtil;
import cn.yunyichina.openapi.proxy.utils.HttpServerUtil;
import cn.yunyichina.openapi.proxy.utils.RegexUtil;
import cn.yunyichina.openapi.proxy.utils.ServiceUtil;
import cn.yunyichina.utils.convert.JsonUtils;
import cn.yunyichina.utils.log.Log;
import cn.yunyichina.utils.log.constant.LogParamType;
import cn.yunyichina.utils.log.constant.LogScene;
import cn.yunyichina.utils.security.Base64Method;

import com.alibaba.dubbo.rpc.service.GenericService;
import com.alibaba.fastjson.JSON;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import io.netty.util.CharsetUtil;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HttpServerHandler extends SimpleChannelInboundHandler<Object> {

    private static final Logger logger = LoggerFactory.getLogger(HttpServerHandler.class);

    /* 此变量用来存储最终返回给用户的数据 */
    private String responseStr = "";

    /* 是否需要关闭当前的http请求 */
    private boolean closeHttp = false;

    /* 解密post报文要用到的 */
    private static final HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); // Disk

    private HttpPostRequestDecoder decoder;
    
    long beginTime = 0;
    long endTime=0;
    private String printAllStr="";

    public void channelReadComplete(ChannelHandlerContext ctx) {
        if (decoder != null) {
            decoder.cleanFiles();
        }
        ctx.flush();
    }

    /**
     * 打印请求信息
     *
     * @param request
     */
    private void printRequestBuffer(HttpRequest request, Object msg) {
        StringBuilder buffer = new StringBuilder();
        buffer.append("\r\n####################### BEGIN REQUEST #######################\r\n");
        buffer.append("HOST: ").append(request.headers().get(HttpHeaderNames.HOST, "unknown")).append("\r\n");
        buffer.append("PROTOCOL_VERSION: ").append(request.protocolVersion()).append("\r\n");
        buffer.append("REQUEST_METHOD: ").append(request.method()).append("\r\n");
        buffer.append("REQUEST_URI: ").append(request.uri()).append("\r\n");
        String postData = request.uri();

        printAllStr="请求URL:"+postData;
        
        HttpHeaders headers = request.headers();
        if ((headers != null) && !headers.isEmpty()) {
            for (Map.Entry<String, String> header : headers) {
                buffer.append("HEADER: ").append(header.getKey()).append(" = ").append(header.getValue()).append("\r\n");
            }
        }

        if (request.method().equals(HttpMethod.POST)) {
            HttpContent httpContent = (HttpContent) msg;
            ByteBuf content = httpContent.content();
            postData = content.toString(CharsetUtil.UTF_8);
            buffer.append(postData).append("\r\n");
            printAllStr=printAllStr+";  POST请求数据:"+postData;
        }

        // 解密base64数据
        String baseDecryptData="";
        if (postData.contains("data=")) {
            try {
                String base64Data = postData.substring(postData.indexOf("data=") + 5);
                if (base64Data.indexOf("&") > 0) {
                    base64Data = base64Data.substring(0, base64Data.indexOf("&"));
                }
                baseDecryptData=Base64Method.decryptUrlSafeBase64(URLDecoder.decode(base64Data,"utf-8"));
                buffer.append("data解密:" + baseDecryptData + "\r\n");
            } catch (Exception ex) {
                logger.error("打印异常:", ex);
            }
        }
        buffer.append("####################### END REQUEST #######################");
        logger.info(buffer.toString());
//        Log.i(LogScene.PROXY,"",LogParamType.REQUEST,-1,buffer.toString());
        printAllStr=printAllStr+";  明文数据:"+baseDecryptData;
    }

    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
    	
    	beginTime = System.currentTimeMillis();
    	responseStr = "";
        HttpRequest request = (HttpRequest) msg;
        
        URI uri = new URI(request.uri());
        if (uri.getPath().equals("/favicon.ico")) {
        	closeHttp = true;
        	writeResponse(request, ctx);
            return;
        }

        // 1.判断解码是否失败
        if (request.decoderResult().isFailure()) {
            responseStr = JsonUtils.toJsonString(new Response("4", "解码失败"));
            closeHttp = true;
            writeResponse(request, ctx);
            return;
        }

        // 2.打印请求报文
        printRequestBuffer(request, msg);

        // 3.根据url判断请求的类型
        /* 请求类型 1-第三方对接接入 2-回调 3-内部app接入 */
        int requestType;
        if (uri.getPath().equals("/openapi/rest")) {
            requestType = 1;
        } else if (uri.getPath().equals("/openapi/gateway")) {
            requestType = 3;
        } else {
            requestType = 2;
            String urlpath = uri.getPath().substring(1);
            String[] urlsplits = urlpath.toLowerCase().split("/");
            if (urlsplits.length < 4) {
                responseStr = JsonUtils.toJsonString(new Response("5", "url路径错误"));
                closeHttp = true;
                writeResponse(request, ctx);
                return;
            }
        }

        // 4.获取用户Http Get/Post 传入的参数
        Map<String, String> httpParams = new HashMap<>();
        if (request.method().equals(HttpMethod.GET)) {// http请求Get方式
            QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri());
            Map<String, List<String>> httpGetParams = queryStringDecoder.parameters();
            try {
                httpParams = HttpServerUtil.convertMaplistToMap(httpGetParams);
            } catch (Exception ex) {
                responseStr = ex.getMessage();
                closeHttp = true;
                writeResponse(request, ctx);
                return;
            }
        } else if (request.method().equals(HttpMethod.POST)) {// http请求Post方式
            decoder = new HttpPostRequestDecoder(factory, request);
            try {
                boolean postDecodeSuccessful = false;
                while (decoder.hasNext()) {
                    // 解释http post数据
                    postDecodeSuccessful = true;
                    InterfaceHttpData interfaceHttpData = decoder.next();
                    if (interfaceHttpData != null) {
                        try {
                            if (interfaceHttpData.getHttpDataType() == HttpDataType.Attribute) {
                                Attribute attribute = (Attribute) interfaceHttpData;
                                String value = null;
                                try {
                                    value = attribute.getValue();
                                } catch (IOException e1) {
                                    e1.printStackTrace();
                                }
                                String key = attribute.getName();
                                httpParams.put(key, value);
                            }
                        } finally {
                            interfaceHttpData.release();
                        }
                    }
                }

                //如果第一种方式获取不了参数，用这种方式，适应回调的情况
                if (!postDecodeSuccessful) {
                    HttpContent httpContent = (HttpContent) msg;
                    ByteBuf content = httpContent.content();
                    String postData = content.toString(CharsetUtil.UTF_8);
                    String[] urlSplits = uri.getPath().substring(1).toLowerCase().split("/");
                    String callMethod = HttpServerUtil.getMethod(urlSplits);
                    httpParams.put("postData", Base64Method.encryptNormalBase64(postData));
                    httpParams.put("method", callMethod);
                }

            } catch (EndOfDataDecoderException e1) {
            } catch (Exception e) {
                e.printStackTrace();
                closeHttp = true;
                responseStr = JsonUtils.toJsonString(new Response("6", "解释HTTP POST协议出错:" + e.getMessage()));
                writeResponse(request, ctx);
                return;
            }
        } else {
            closeHttp = true;
            responseStr = JsonUtils.toJsonString(new Response("6", "接口只支持HTTP/HTTPS GET/POST协议"));
            writeResponse(request, ctx);
            return;
        }
        httpParams.put("request_type", String.valueOf(requestType));

        //5.权限的校验,回调授权直接返回成功
        Response response = AuthorizeUtil.validate(httpParams);
        if (!"0".equals(response.getResultCode())) {
            closeHttp = true;
            responseStr = JsonUtils.toJsonString(response);
            writeResponse(request, ctx);
            return;
        }

        // 6.调用dubbo服务
        responseStr = callDubboService(httpParams, requestType, uri.getPath().substring(1));

        // 7.写回给客户端
        writeResponse(request, ctx, requestType, httpParams);
    }

    /**
     * 调用远程服务
     *
     * @param httpParams
     * @param requestType
     * @return
     * @throws Exception
     */
    private String callDubboService(Map<String, String> httpParams, int requestType, String urlPath) throws Exception {
        String result;

        if (httpParams.size() == 0) {
            result = JsonUtils.toJsonString(new Response("7", "参数不能为空"));
            return result;
        }

        // 对于回调，需要自己组合参数
        if (requestType == 2) {
            /* 获取回调地址对应系统的方法 */
            String[] urlSplits = urlPath.toLowerCase().split("/");
            String callMethod = HttpServerUtil.getMethod(urlSplits);

            //去掉验证的参数
            httpParams.remove("method");
            httpParams.remove("request_type");
            String data = JSON.toJSONString(httpParams);

            httpParams.clear();
            httpParams.put("data", Base64Method.encryptUrlSafeBase64(data));
            httpParams.put("method", callMethod);
            httpParams.put("sessionid", "");
        }

        if (!httpParams.containsKey("method")) {
            result = JsonUtils.toJsonString(new Response("9", "参数并不包含method，请检查提交的参数"));
            return result;
        }

        String appId=httpParams.containsKey("app_id")?httpParams.get("app_id"):"";
        String methodName = httpParams.get("method").toLowerCase();
        String serviceDefine = HttpServerUtil.getServiceDefine(methodName);
        if (HttpServerUtil.isEmpty(serviceDefine)) {
            result = JsonUtils.toJsonString(new Response("9", "接口method参数出错，请检查提交的参数"));
            return result;
        }

        if (!ServiceUtil.getDicServices().containsKey(serviceDefine)) {
        	//重新加载
        	ServiceUtil.reLoad(serviceDefine);
	        if (!ServiceUtil.getDicServices().containsKey(serviceDefine)){
	            result = JsonUtils.toJsonString(new Response("10", "没有该方法的服务，请注册到数据库"));
	            return result;
        	}
        }

        GenericService service = (GenericService)ServiceUtil.getDicServices().get(serviceDefine).getService();
        try {
            //脱敏还原,这段代码由于以前把解密base放在公共类库了
            //所以以下操作才那么麻烦
            if(httpParams.containsKey("data")){
                String notBase64Str=Base64Method.decryptUrlSafeBase64(httpParams.get("data"));
                String descryptStr= FilterHandler.filterRequestJson(methodName,notBase64Str);
                httpParams.put("data",Base64Method.encryptUrlSafeBase64(descryptStr));
            }

        	Object dubboResponseObj = service.$invoke("callFromExternal", new String[] { "java.util.Map" }, new Object[]{httpParams});
            Response dubboResponse=JSON.parseObject(JSON.toJSONString(dubboResponseObj),Response.class);
        	if (requestType == 2) {
                result = dubboResponse.getResult();
            } else {
        	    //脱敏
        	    String filterResponse= FilterHandler.filterResponseJson(appId,methodName,dubboResponse.getResult());
                result = "{\"result_code\":\"" + dubboResponse.getResultCode() + "\",\"result_message\":\""
                        + dubboResponse.getResultMessage() + "\",\"result\":" + filterResponse + "}";
            }
        } catch (Exception e) {
            logger.error("调用dubbo异常:", e);
            result = JsonUtils.toJsonString(new Response("6", "调用远程方法失败,异常" + e.getMessage()+",请检查子系统是否提供服务"));
        }

        if (result == null) {
            result = JsonUtils.toJsonString(new Response("6", "调用远程方法失败"));
        }

        return result;
    }

    private void writeResponse(HttpRequest request, ChannelHandlerContext ctx) throws URISyntaxException {
        writeResponse(request, ctx, 0, null);
    }

    /**
     * 将结果返回给用户
     *
     * @param request
     * @param ctx
     * @param requestType
     * @param httpParams
     * @throws URISyntaxException
     */
    private void writeResponse(HttpRequest request, final ChannelHandlerContext ctx, int requestType,
                               Map<String, String> httpParams) throws URISyntaxException {
        // 1.根据不同的请求类型，分析参数jsCallBackMethod 和 redirectFlag
        /* 解决js跨域的问题 */
        String jsCallBackMethod = "";
        /* 是否重定向标志，如果是重定向，则返回tex/html 0-不 1-重定向 */
        int redirectFlag = 0;
        if (httpParams != null) {
            if (requestType == 2) {
                URI uri = new URI(request.uri());
                String urlpath = uri.getPath().substring(1);
                String[] urlsplits = urlpath.toLowerCase().split("/");
                /*
                 * 如果是进行回调，并且最后的那个是front的，则代表是前台跳转方式
                 * 比如:yyt.base.user.resource.delete.front
                 */
                if (urlsplits[urlsplits.length - 1].contains("front")) {
                    redirectFlag = 1;
                }
            } else {
                /* 判断请求的参数里面是否包含callback回调的字段 */
                if (httpParams.containsKey("callback")) {
                    jsCallBackMethod = httpParams.get("callback");
                }

                /* 判断请求的参数里面是否包含redirectflag重定向的字段 */
                if (httpParams.containsKey("redirectflag")) {
                    redirectFlag = Integer.parseInt(httpParams.get("redirectflag"));
                }
            }
        }

        /* 2.当不是主动关闭http通道的时候检测 */
        if (!closeHttp) {
            if (request.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE, true)) {
                closeHttp = true;
            } else if (request.protocolVersion().equals(HttpVersion.HTTP_1_0)) {
                closeHttp = true;
            }
        }

        /* 3.当closehttp为flase的时候 */
        boolean keepAlive = false;
        if (!closeHttp) {
            keepAlive = HttpUtil.isKeepAlive(request);
        }

        if (!jsCallBackMethod.isEmpty()) {
            responseStr = jsCallBackMethod + "(" + responseStr + ")";
        }

        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, request.decoderResult()
                .isSuccess() ? HttpResponseStatus.OK : HttpResponseStatus.BAD_REQUEST, Unpooled.copiedBuffer(
                responseStr.toString(), CharsetUtil.UTF_8));

        if (requestType == 2) {
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
        } else {
            if (redirectFlag == 1) {
                response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
            } else {
                response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");
            }
        }

        if (keepAlive && HttpServerConfig.getConfig().getSupportkeepalive()) {
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        } else {
            request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
        }

        endTime = System.currentTimeMillis();
//        printAllStr=printAllStr+";  调用结果返回:"+responseStr+"|| 耗时:"+(endTime-beginTime)+"毫秒";
//        logger.info(printAllStr);
        printAllStr=printAllStr+";  调用结果返回:"+responseStr;

        String method="";
        if(httpParams!=null){
            method=httpParams.containsKey("method")?httpParams.get("method"):"";
        }
        Log.i(LogScene.PROXY,method,LogParamType.NORMAL,endTime-beginTime,printAllStr);

        /* 是否支持http的keep-alive */
        if (!HttpServerConfig.getConfig().getSupportkeepalive()) {
            closeHttp = true;
        }

        final ChannelFuture future = ctx.writeAndFlush(response);

        future.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture channelFuture) {
                assert future == channelFuture;
                ctx.close();
                logger.info("Operation completed.");
            }
        });
//        if (future.isSuccess()) {
//            future.addListener(ChannelFutureListener.CLOSE);
//        } else {
//            future.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
//        }
    }

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        logger.info("Request Is Exception:" + cause.getMessage());
        ctx.close();
    }

}
